'''
Created on Jul 29, 2010

@author: adolfo

Implementation of the SMSGateway interface for the Gammu SMS gateway. For more
information about Gammu, visit `its homepage
<http://wammu.eu>`_.
'''
from sms_gateway import Message, SMSGateway
from springpython.database.core import RowMapper, DatabaseTemplate
import SocketServer
import logging
import threading

_LOGGER = logging.getLogger("mx.org.came.gammu_sms_gateway")

class GammuSMSGateway(SMSGateway):
    """Implementation of SMSGateway for the Gammu SMS gateway. In order to
    receive messages, this class starts a UDP server at the specified port and
    waits for notifications from Gammu (that runs a script named
    noticeServer.py from a batch file to send the row index where the message
    was stored in Gammu's database). 
    
    Note: It is recommended that only one instance of this class is created.
    """
    
    MESSAGE_RETRIEVAL_QUERY_SQL = """
            SELECT `SenderNumber`, `UDH`, `Text`, `TextDecoded`
            FROM `inbox`
            WHERE ID = %s
            """
            
    MESSAGE_DELIVERY_QUERY_SQL = """
            INSERT INTO `outbox` (`DestinationNumber`, `UDH`, `Text`
            , `TextDecoded`, `Coding`
            , `ID`, `UpdatedInDB`, `InsertIntoDB`
            , `SendingDateTime`, `Class`,  `MultiPart`, `RelativeValidity`
            , `SenderID`, `SendingTimeOut`, `DeliveryReport`, `CreatorID`)
            VALUES (%s, %s, %s
            , %s, %s
            , NULL, CURRENT_TIMESTAMP, '0000-00-00 00:00:00'
            , '0000-00-00 00:00:00', '-1', 'false', '-1'
            , NULL, '0000-00-00 00:00:00', 'default', ''
            )"""
    
    def __init__(self
                 , gammu_db_connection_factory
                 , notification_server_address = "localhost"
                 , notification_server_port = 20000
                 , gammu_phone_number = 1234
                 , gammu_message_retrieval_query = MESSAGE_RETRIEVAL_QUERY_SQL
                 , gammu_message_delivery_query = MESSAGE_DELIVERY_QUERY_SQL):
        """Initializes the instance of GammuSMSGateway. Sets the listener list
        to the empty list. The message notification server's address defaults to
        "localhost" and its port to 20000 (this is where the instance of
        GammuSMSGateway will be notified). The phone number of the GSM modem
        Gammu uses to send and receive SMS messages defaults to 1234.
        gammu_db_connection_factory is an instance of
        springpython.database.factory.ConnectionFactory, used to query
        Gammu's database. gammu_message_retrieval_query is the SQL query that
        will be used to retrieve a message from the database given its id in
        the table (see MESSAGE_RETRIEVAL_QUERY_SQL).gammu_message_delivery_query
        is the SQL query that will be used to store messages to Gammu's outbox
        for delivery (see MESSAGE_DELIVERY_QUERY_SQL). If you
        choose to replace any of the queries the order of the columns must
        remain the same as in the defaults.
        """
        _LOGGER.debug("Initializing GammuSMSGateway")
        self._listeners = []
        self._gammu_phone_number = gammu_phone_number
        self._database_template = DatabaseTemplate(gammu_db_connection_factory)
        self._gammu_message_retrieval_query = gammu_message_retrieval_query
        self._gammu_message_delivery_query = gammu_message_delivery_query
        # Nested class that handles the request. Attribute 'owner' should be an
        # instance variable instead of a class variable, but the constructor for
        # BaseHTTPRequestHandler should not be overridden..
        # This definition makes _HttpHandler behave as a Java inner class.
        # (as far as I have seen, on the implementation of Python I'm using)
        class _ThreadedUDPRequestHandler(SocketServer.BaseRequestHandler):
            """Handles Gammu's notifications. They indicate that an entry
            in the database has been added and its ID."""
            owner = self
            def handle(self):
                _LOGGER.debug("Message arrived. Handling... ")
                sms_id = self.request[0]
                _LOGGER.debug("ID of SMS in inbox: " + str(sms_id))
                message = self.owner._get_message_from_db(sms_id)
                self.owner._notify_message_reception(message)
        self._notification_server = _ThreadedUDPServer(
                (notification_server_address, notification_server_port)
                , _ThreadedUDPRequestHandler)
        self._server_thread = None

    def get_phone_number(self):
        """Returns the phone number of the GSM modem Gammu uses to send and
        receive SMS messages.
        """
        return self._gammu_phone_number
    
    def add_sms_listener(self, sms_listener):
        """Registers an SMSListener to be notified about SMS message events."""
        _LOGGER.debug("Adding new SMSListener to GammuSMSGateway")
        self._listeners.append(sms_listener)
    
    def remove_sms_listener(self, sms_listener):
        """Removes an SMSListener from the list of registered listeners that
        are notified about SMS message events. Only the first appearance of the
        instance is removed.
        """
        _LOGGER.debug("Removing SMSListener from GammuSMSGateway")
        self._listeners.remove(sms_listener)

    def _get_message_from_db(self, entry_id):
        """Receives the the id of the SMS message in Gammu's database, 
        retrieves the tuple and stores the data in an instance of
        sms_gateway.Message. The query used by default is
        MESSAGE_RETRIEVAL_QUERY_SQL
        """
        class MessageMapper(RowMapper):
            """Wraps the entries in Gammu's database in instances of Message"""
            def map_row(self, row, metadata=None):
                """Maps a row from a query to Gammu's database to an instance
                of Message. The columns mapped are in the order used by the
                default query: GammuSMSGateway.MESSAGE_RETRIEVAL_QUERY_SQL
                """
                message = Message()
                message.data[Message.SENDER_PHONE_NUMBER] = row[0]
                message.data[Message.UDH] = row[1].decode("hex_codec")
                # Set ORIGINAL_BINARY_MESSAGE to the binary message if the UDH
                # is not empty and to the decoded text if it is.
                message.data[Message.ORIGINAL_BINARY_MESSAGE] = \
                        row[2].decode("hex_codec") if row[1] else row[3]
                return message
        results = self._database_template.query(
                self._gammu_message_retrieval_query
                , (entry_id,)
                , MessageMapper())
        _LOGGER.debug("Message retrieved by query (in list): %s", results)
        if len(results) != 1:
            _LOGGER.exception("Expected to retrieve one message from Gammu's "
                              + "database. Received: %s", results)
            raise IndexError("Expected unique entry in Gammu's database for id:"
                             + str(entry_id))
        message = results[0]
        return message

    def _notify_message_reception(self, message):
        """Receives an instance of ``sms_gateway.Message`` and notifies all the
        registered listeners of its arrival.
        """
        _LOGGER.debug("Message data: " + str(message.data))
        _LOGGER.debug("Notifying listeners about message arrival.")
        for listener in self._listeners:
            listener.message_received(message, notifier = self)

    #Pylint reports an error in the following method by mistake    
    #pylint: disable=E1101
    def start_service(self):
        """Start the UMD server and wait for notifications from
        Gammu.
        """
        _LOGGER.debug("Starting UDP server to receive messages from Gammu...")
        self._server_thread = threading.Thread(
                target = self._notification_server.serve_forever)
        self._server_thread.setDaemon(True)
        self._server_thread.start()
        _LOGGER.info("Started UDP server to receive messages from Gammu.")
    
    #Pylint reports an error in the following method by mistake    
    #pylint: disable=E1101
    def stop_service(self):
        """Stops the UDP server that listens for notifications from Gammu."""
        _LOGGER.info("Stopping UDP server that received messages from Gammu.")
        self._notification_server.shutdown()
        _LOGGER.info("Server stopped.")
    
    def send_message(self, message):
        """Send a message via SMS to the specified telephone number.
        This is done by writing a tuple into the outbox table of Gammu's
        database. The query used by default is: MESSAGE_DELIVERY_QUERY_SQL
        """
        _LOGGER.info("Sending message: %s", message.data)
        self._database_template.execute(self._gammu_message_delivery_query
                   , (message.data[Message.RECEIVER_PHONE_NUMBER]
                      , message.data[Message.UDH].encode("hex_codec")
                      , message.data[Message.ORIGINAL_BINARY_MESSAGE].encode("hex_codec") if message.data[Message.UDH] else ""
                      , message.data[Message.ORIGINAL_BINARY_MESSAGE] if not message.data[Message.UDH] else ""
                      , "8bit" if message.data[Message.UDH] else "Default_No_Compression"))
        _LOGGER.debug("Message written to Gammu's outbox.")

class _ThreadedUDPServer(SocketServer.ThreadingMixIn, SocketServer.UDPServer):
    """A threaded UDP server used to receive message arrival notifications
    from Gammu.
    """
    pass
